home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Scene 96
/
Scene 96 International Edition (Zyklop Software) (Disc 2) (1997).iso
/
misc
/
coding
/
vgacodng
/
part06.txt
< prev
next >
Wrap
Text File
|
1996-08-07
|
16KB
|
368 lines
VGA-Kurs - Part #6
'T.C.P.s Beginner's Guide To VGA Coding', Part VI ist da!
Heute wollen wir uns über ein Thema unterhalten, mit dem viele Anfänger
Schwierigkeiten haben. Sie wollen ein Programm schreiben. Sie wissen auch, wie
sie im Grafikmodus ein paar Punkte setzen können, aber das hilft ihnen nicht
weiter, denn sie wollen in ihr Programm ein Bild einbauen. Entweder haben sie
es selbst gezeichnet oder zeichnen lassen oder was auch immer. Auf jeden Fall
wollen sie es unbedingt in ihrem Programm haben, und zwar möglichst
professionell, also nicht durch ein Hintertürchen, indem sie einen externen
Viewer wie PicView dazupacken und an der entsprechenden Stelle im Programm mit
der EXEC-Prozedur aufrufen.
Also, wie mache ich das jetzt, ein Bild anzuzeigen?
Tja, erst mal brauchen wir ein geeignetes Format, das wir leicht einlesen und
anzeigen können. Das simpelste Format von allen, auch wenn es mehr oder weniger
inoffiziell ist, ist das RAW-Format. Der Aufbau dieses Formats ist in einem
Satz erklärt: Alle Daten des Bildes werden in einem Rutsch vom
Bildschirmspeicher in eine Datei geschrieben. Das Anzeigen eines solchen
Bildes ist ebenso einfach: Alle Daten werden aus der Datei nacheinander zurück
in den Bildschirmspeicher geschrieben. Dies sieht nun in Pascal so aus:
procedure SaveRAW(Name:string);
var f : file;
begin
assign(f,Name);
rewrite(f,1); { Datei erstellen }
blockwrite(f,mem[$A000:0],64000);
{ Ab Adresse $A000:0 64000 Byte lesen und in die Datei schreiben }
close(f); { Datei wieder schließen }
end;
procedure LoadRAW(Name:string);
var f : file;
begin
assign(f,Name);
reset(f,1); { Datei für Lesezugriff vorbereiten }
blockread(f,mem[$A000:0],64000);
{ 64000 Byte aus Datei lesen und in den Bildschirmspeicher schreiben }
close(f);
end;
Da hier allerdings nur die Bildinformationen gespeichert werden, und nicht die
notwendige Palette, müssen wir diese auch noch sichern:
procedure GetPal(col:byte;var r,g,b:byte);
begin
port[$3C7] := Col;
r := port[$3C9];
g := port[$3C9];
b := port[$3C9];
end;
procedure SetPal(col:byte;r,g,b:byte);
begin
port[$3C8] := Col;
port[$3C9] := r;
port[$3C9] := g;
port[$3C9] := b;
end;
procedure SavePal(Name:string);
var f : file;
n : byte;
RGB : array[1..3] of byte;
begin
assign(f,Name);
rewrite(f,1);
for n := 0 to 255 do begin
getpal(n,RGB[1],RGB[2],RGB[3]);
blockwrite(f,RGB,3);
end;
close(f);
end;
procedure LoadPal(Name:string);
var f : file;
n : byte;
RGB : array[1..3] of byte;
begin
assign(f,Name);
reset(f,1);
for n := 0 to 255 do begin
blockread(f,RGB,3);
setpal(n,RGB[1],RGB[2],RGB[3]);
end;
close(f);
end;
Man kann die Prozeduren auch kombinieren, indem man die Palettendaten an die
RAW-Datei anhängt. So macht es das (unkomprimierte) TGA-Format auch. Allerdings
speichert es die Palettendaten in der Reihenfolge Blau, Grün, Rot ab. Außerdem
ist jeder Farbwert mit 4 multipliziert.
Jetzt stehen wir vor einem weiteren Problem: Da das RAW-Format kaum verbreitet
ist, gibt es kein Konvertierprogramm (außer Paintshop Pro 3.0, allerdings
speichert es die RAWs aus unerfindlichen Gründen falschrum ab), mit dem man
seine Bilder in RAWs konvertieren kann. Also müssen wir uns selbst darum
kümmern. Beim folgenden Konvertierer habe ich mich für PCX als Eingabeformat
entschieden. Das GIF-Format ist zwar wesentlich mehr verbreitet, darf aber
aus allseits bekannten Gründen hier nicht zur Anwendung kommen.
program PCX2RAW;
uses crt;
type TPCXHeader = record { Header der PCX-Datei }
Manuf,Version,Encode,BitsPerPixel : byte;
X1,Y1,X2,Y2,Xres,Yres : integer;
Palette : array[0..47] of byte;
VideoMode,Planes : byte;
BytesPerLine : integer;
Reserved : array[0..59] of byte;
end;
PPCXPic = ^TPCXPic;
TPCXPic = record
Header : TPCXHeader; { Der Header }
Palette : array[0..767] of byte; { Die Palette }
Pixels : pointer; { Das Bild }
end;
var PCX_ : TPCXPic;
I : integer;
palf,rawf : file;
PCX,PAL,RAW : string;
procedure LoadPCX(FileName:string;var PCX:TPCXPic); { Lädt PCX-Datei }
var F : file;
Buf : array[0..1024] of byte;
BufPtr,Off,Size : word;
Code,Count : byte;
begin
assign(F,FileName);
reset(F,1);
blockread(F,PCX.Header,sizeof(PCX.Header)); { Header einlesen }
with PCX.Header do { und auswerten }
if (Manuf <> 10) or (Version <> 5) or (Encode <> 1) or
(BitsPerPixel <> 8) or (Planes <> 1) or
(BytesPerLine > 320) or (Y2 - Y1 > 199) then begin
PCX.Pixels := nil; { Bild kann nicht dargestellt werden }
exit;
end;
Size := PCX.Header.BytesPerLine * succ(PCX.Header.Y2 - PCX.Header.Y1);
{ Bildgröße ermitteln }
getmem(PCX.Pixels,Size);
if PCX.Pixels = nil then exit;
BufPtr := sizeof(Buf);
Off := 0; { Offset in der PCX-Datei }
while Off < Size do begin
if BufPtr >= sizeof(Buf) then begin
blockread(F,Buf,sizeof(Buf)); { Daten lesen }
BufPtr := 0;
end;
Code := Buf[BufPtr];
inc(BufPtr);
if Code shr 6 = 3 then begin { Dekomprimierung }
Count := Code and 63;
if BufPtr >= sizeof(Buf) then begin
blockread(F,Buf,sizeof(Buf));
BufPtr := 0;
end;
Code := Buf[BufPtr];
inc(BufPtr);
fillchar(mem[Seg(PCX.Pixels^):ofs(PCX.Pixels^)+Off],Count,Code);
inc(Off,Count);
end
else begin
mem[seg(PCX.Pixels^):ofs(PCX.Pixels^)+Off] := Code;
inc(Off);
end;
end;
if BufPtr >= sizeof(Buf) then begin
blockread(F,Buf,sizeof(Buf));
BufPtr := 0;
end;
Code := Buf[BufPtr];
inc(BufPtr);
if Code = 12 then begin
for Off := 0 to 767 do begin
if BufPtr >= sizeof(Buf) then begin
blockread(F,Buf,767-Off);
BufPtr := 0;
end;
PCX.Palette[Off] := Buf[BufPtr];
inc(BufPtr);
end;
end;
close(F);
end;
procedure FreePCX(var PCX:TPCXPic);
begin
if PCX.Pixels <> nil then
freemem(PCX.Pixels,PCX.Header.BytesPerLine*succ(PCX.Header.Y2-PCX.Header.Y1));
end;
begin
if paramcount <> 2 then halt;
PCX := paramstr(1); { Name der PCX-Datei }
RAW := paramstr(2); { Name der RAW-Datei }
PAL := RAW; { Name der PAL-Datei }
delete(PAL,pos('.',PAL),4); { eventuelle RAW-Endung entfernen }
PAL := PAL + '.pal'; { Endung '.PAL' anhängen }
LoadPCX(PCX,PCX_); { PCX-Datei laden }
if PCX_.Pixels = nil then begin { Fehler beim Laden }
writeln(#13#10'Error reading PCX file: ',PCX);
halt;
end;
asm mov ax,13h; int 10h end; { Modus 13h setzen }
port[$3C8] := 0; { Palette setzen }
for I := 0 to 767 do begin
PCX_.Palette[I] := PCX_.Palette[I] shr 2;
Port[$3C9] := PCX_.Palette[I];
end;
with PCX_ do { Bild darstellen }
for I := Header.Y1 to Header.Y2 do
Move(mem[seg(PCX_.Pixels^):ofs(PCX_.Pixels^)+I*Header.BytesPerLine],
mem[$A000:320*I],Header.X2 - Header.X1 + 1);
assign(rawf,RAW); { Dateien vorbereiten }
rewrite(rawf,1);
assign(palf,PAL);
rewrite(palf,1);
with PCX_ do { RAW-File schreiben }
for I := Header.Y1 to Header.Y2 do
blockwrite(rawf,mem[$A000:320*I],Header.X2 - Header.X1 + 1);
blockwrite(palf,PCX_.Palette,768); { PAL-File schreiben }
readkey;
close(rawf);
close(palf);
textmode(3);
end.
Ein wirklich langer Quelltext, aber leider ein Muß.
Aufgerufen wird das Programm folgendermaßen: PCX2RAW [PCXFile] [RAWFile].
Es folgt nun die Aufschlüsselung des PCX-Formats, wahrscheinlich nur für
etwas fortgeschrittenere Leser interessant, aber trotzdem:
Am Anfang der PCX-Datei steht ein 128 Byte langer Header, in dem sämtliche
Informationen über die Eigenschaften des Bildes stehen. Hier die Offsets:
Offset Bytes Bedeutung
--------------------------
0 1 Identifikationsbyte. Enthält den Wert 10, wenn PCX-Datei.
1 1 Versionsnummer. Werte:
0 : Version 2.5
2 : Version 2.8 mit Palettendaten
3 : Version 2.8 ohne Palettendaten
5 : Version 3.0
2 1 Komprimierungsinfo. Werte:
0 : Bild nicht gepackt
1 : Bild gepackt
3 1 Bits pro Pixel (8 für 256 Farben)
4 2 Linke obere X-Koord
6 2 Linke obere Y-Koord
8 2 Rechte untere X-Koord
10 2 Rechte untere Y-Koord
12 2 X Auflösung
14 2 Y Auflösung
16-65 59 Palette für 16-Farben-Bilder
64 1 Videomodus
65 1 Anzahl der Farbebenen (Planes)
66 2 Bytes pro Zeile
68 2 Paletteninfo. Werte:
1 : Mono/Farbe
2 : Graustufen
70-127 57 Bisher unbenutzt
Jetzt zum Kompressionsverfahren. Es funktioniert folgendermaßen:
Sind in einem Datenbyte die zwei obersten Bits gesetzt, ist es ein Zählerbyte,
d.h. die unteren 6 Bytes bilden einen Wert von 1 bis 63, der bestimmt, wie
oft sich das darauffolgende Byte wiederholt. Ist dagegen nur eins bzw. gar
keins der beiden Bits gesetzt, kann das Byte so wie es ist in den
Bildschirmspeicher eingelesen werden.
Das Verfahren nennt sich übrigens Lauflängenkodierung, zu Englisch Run Length
Encoding (RLE).
Was aber, wenn man unkomprimiertes Byte hat, in dem die obersten Bits gesetzt
sind, z.B. 255? Dann muß man wohl oder übel ein Zählerbyte erzeugen, das
anzeigt, daß einmal der Wert 255 eingelesen werden muß, womit ein Byte
verschwendet wäre. Ein Beispiel:
Unkomprimiert: 00h 00h 00h 00h 01h 05h FFh
Komprimiert: C4h 00h 01h 05h C1h FFh
Hier werden 2 Bytes gespart, weil wir die 4 '00h' zu 'C4h 00h' zusammenfassen
können. Allerdings geht auch ein Byte wieder verloren, da bei 'FFh' die oberen
beiden Bits gesetzt und wir 'C1h FFh' schreiben müssen.
Das maximale Kompressionsverhältnis beträgt also 1 zu 32 (es können 64 Byte zu
2 Byte zusammengefaßt werden). Allerdings kann es im ungünstigsten Fall auch
zu einer Vergrößerung des Umfangs der Daten kommen.
Da wir jetzt aber das PCX-Format einlesen können, wozu brauchen wir dann noch
das RAW-Format? Nun, es liegt wohl auf der Hand, daß ein einziges Blockread
sehr viel einfacher und auch schneller ist, als der gesamte obige Code für das
Einlesen einer PCX-Grafik. Außerdem ist es ein wenig professioneller, wenn man
für seine Programm ein Format benutzt, das nicht jeder mit einem Viewer
einsehen oder die Bilder klauen kann. Trotzdem hat das RAW-Format einen
gewichtigen Nachteil: Es ist zu groß.
Dem kann man abhelfen, indem man sich einen Kompressionsalgorithmus schreibt,
ähnlich wie oben beschrieben. Das ist nicht so schwer wie es sich anhört. Ich
zum Beispiel benutze für das DFI-Format (Diabolic Force Image) einen
Algorithmus, der in weniger als 30 Zeilen Platz findet. Trotzdem packt er recht
gut.
Ein eigenes Format ist also nicht von Nachteil. Da das Kompressionsverfahren
von PCX nicht geschützt ist, könnten wir es dafür verwenden, aber wir wollen
doch mal sehen, ob wir es nicht noch verbessern können.
Die Lauflängenkodierung, wie sie oben beschrieben ist, stellt schon mal einen
guten Anfang dar. Jedoch sollte man darüber nachdenken, statt der oberen
2 Bits die obersten 3 Bits zur Markierung eines Zählerbytes zu benutzen.
Die 3 Bits könnten dann folgendes bedeuten:
000xxxxx : X Null-Bytes (kommen sehr haüfig vor)
001xxxxx : X mal das folgende Byte
010xxxxx : Die nächsten X Bytes unverändert in den Speicher laden
011xxxxx : X mal die folgenden 2 Bytes
100xxxxx : Die 5 Restbits & die 8 Bits des nächsten Bytes als Zähler für
das übernächste Byte (13 Bit = 1-8191)
usw.
Für den Rest könnt ihr euch selbst was passendes einfallen lassen.
Das obige Verfahren dürfte ca. 5-10%, bei weniger komplexen Bildern bis zu
30% mehr Kompression einbringen.
Ein anderes komplexes Verfahren, das ebenso viele Freunde wie Feinde haben
dürfte, ist das JPEG-Format. Es komprimiert die Bilddaten wie kein anderes
Format, allerdings mit dem Nachteil, das Bildinformationen verloren gehen.
Außerdem ist die Kompression bzw. Dekompression so aufwendig und langsam, daß
es sich kaum für Spiele oder andere Programme eignen dürfte. Wenn ihr also
nicht gerade einen Viewer schreiben wollt, könnt ihr darauf verzichten.
Ein weiterer Bereich, in dem die Kompressionsmethoden immer mehr verbessert
werden, ist der der digitalen Videos. Hier konnte sich das MPEG-Verfahren, das
mit JPEG verwandt ist, einigermaßen durchsetzen, jedoch sind hierfür teure
Zusatzkarten erforderlich, wenn man nicht gerade einen ultraschnellen PC hat.
Für Animationen gibt es außerdem das bekannte FLI-Format, bzw. dessen
Weiterentwicklung, das FLC. Hier kommt eine Technik zum Einsatz, bei der nur
die Daten gespeichert werden, die zur vorigen Frame (Einzelbild) unidentisch
sind. Die erste Frame wird also komplett gespeichert (ebenfalls mit einer Art
Lauflängenkodierung). Danach folgen die nächsten Frames, wobei jede einen
eigenen Header besitzt, in dem steht, wie groß und von welchem Typ sie ist.
Falls sich die Palette der Frame geändert hat, enthält der darauffolgende
Datenteil als erstes eine gepackte Palette. Anschließend folgen die Daten,
allerdings nur die, die unterschiedlich zur vorigen Frame sind.
Sehr gute und ausfürliche Beschreibungen von Grafik- und Animations-, aber auch
Sound-Formaten u.a. finden sich in der PC Games Programmer's Encyclopedia 1.0.
Wer sich also jetzt vorgenommen hat, sein eigenes Bild- oder sogar Video-Format
zu schreiben, dem sei gesagt: Es führt kein Weg am Assembler vorbei.
Außerdem ist ein Algorithmus nie ganz ausgereizt, es gibt immer Wege, ihn zu
verbessern.
Ich hoffe, die Ausführungen in diesem Teil haben euch geholfen, und euch nicht
zu sehr verwirrt.
Was im nächsten Teil kommen wird, weiß ich noch nicht genau, laßt euch einfach
überraschen.
[ This text copyright (c) 1995-96 Johannes Spohr. All rights reserved. ]
[ Distributed exclusively through PC-Heimwerker, Verlag Thomas Eberle. ]
[ ]
[ No part of this document may be reproduced, transmitted, ]
[ transcribed, stored in a retrieval system, or translated into any ]
[ human or computer language, in any form or by any means; electronic, ]
[ mechanical, magnetic, optical, chemical, manual or otherwise, ]
[ without the expressed written permission of the author. ]
[ ]
[ The information contained in this text is believed to be correct. ]
[ The text is subject to change without notice and does not represent ]
[ a commitment on the part of the author. ]
[ The author does not make a warranty of any kind with regard to this ]
[ material, including, but not limited to, the implied warranties of ]
[ merchantability and fitness for a particular purpose. The author ]
[ shall not be liable for errors contained herein or for incidental or ]
[ consequential damages in connection with the furnishing, performance ]
[ or use of this material. ]